package com.gitee.wsl.jvm.okhttp

import com.gitee.wsl.ext.K
import com.gitee.wsl.ext.coroutines.onCancel
import com.gitee.wsl.math.Math
import com.gitee.wsl.ext.okio.base.CallState
import com.gitee.wsl.ext.okio.base.TransferProgress
import com.gitee.wsl.ext.okio.base.progressPercentage
import com.gitee.wsl.ext.runOnScope
import com.gitee.wsl.jvm.ext.base.UTF8
import com.gitee.wsl.jvm.ext.store.FileExt
import com.gitee.wsl.jvm.math.div
import com.gitee.wsl.jvm.okhttp.OkHttpExt.defaultLinuxUA
import com.gitee.wsl.jvm.okhttp.callback.CountingCallback
import com.gitee.wsl.jvm.okhttp.exception.ResponseStatusException
import com.gitee.wsl.jvm.okhttp.intercept.updown.OutputStreamFactory
import com.gitee.wsl.jvm.okhttp.listener.ProgressListener
import com.gitee.wsl.jvm.okhttp.request.CountingRequestBody
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Callback
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import okio.use
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.net.URLEncoder
import java.security.SecureRandom
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import kotlin.coroutines.resumeWithException


val downloadHttpClient:OkHttpClient by lazy{
    okHttpClient {
        ableGzipRequest()
        trustAllCerts()
        onProgress { totalReadBytes, contentLength ->
            Timber.d("on progress $contentLength / $totalReadBytes")
        }
        userAgent(defaultLinuxUA)
    }
}

fun downloadHttpClient(config: (OkHttpClientScope.() -> Unit)?=null,listener: ProgressListener):OkHttpClient{
    return okHttpClient {
        ableGzipRequest()
        trustAllCerts()
        onProgress(listener)
        userAgent(defaultLinuxUA)
        config?.let { it(this) }
    }
}


object OkHttpExt {

    const val AUTHORIZATION = "Authorization"

    const val defaultCacheSize = 5L * 1024 * 1024 // 5 MiB

    const val defaultLinuxUA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76"

    fun createSSLSocketFactory(): SSLSocketFactory {
        return runCatching {
            SSLContext.getInstance("TLS").let {
                it.init(null, arrayOf(TrustAllCerts()), SecureRandom())
                it.socketFactory
            }
        }.getOrElse {
            throw it
        }
    }

    private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, TimeUnit.MINUTES).build()
    private val DEFAULT_HEADERS = Headers.Builder().build()
    private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()

    fun get(
        url: String,
        headers: Headers = DEFAULT_HEADERS,
        cache: CacheControl = DEFAULT_CACHE_CONTROL,
    ): Request {
        return get(url.toHttpUrl(), headers, cache)
    }

    /**
     * @since extensions-lib 1.4
     */
    fun get(
        url: HttpUrl,
        headers: Headers = DEFAULT_HEADERS,
        cache: CacheControl = DEFAULT_CACHE_CONTROL,
    ): Request {
        return Request.Builder()
            .url(url)
            .headers(headers)
            .cacheControl(cache)
            .build()
    }

    fun post(
        url: String,
        headers: Headers = DEFAULT_HEADERS,
        body: RequestBody = DEFAULT_BODY,
        cache: CacheControl = DEFAULT_CACHE_CONTROL,
    ): Request {
        return Request.Builder()
            .url(url)
            .post(body)
            .headers(headers)
            .cacheControl(cache)
            .build()
    }

    fun put(
        url: String,
        headers: Headers = DEFAULT_HEADERS,
        body: RequestBody = DEFAULT_BODY,
        cache: CacheControl = DEFAULT_CACHE_CONTROL,
    ): Request {
        return Request.Builder()
            .url(url)
            .put(body)
            .headers(headers)
            .cacheControl(cache)
            .build()
    }

    fun delete(
        url: String,
        headers: Headers = DEFAULT_HEADERS,
        body: RequestBody = DEFAULT_BODY,
        cache: CacheControl = DEFAULT_CACHE_CONTROL,
    ): Request {
        return Request.Builder()
            .url(url)
            .delete(body)
            .headers(headers)
            .cacheControl(cache)
            .build()
    }


    //==============url 参数=====================//
    /**
     * 重置url上的请求参数
     *
     * @param oldRequest 拦截的旧请求
     * @param key        请求的key
     * @param value      参数值
     * @return
     */
    fun resetUrlParams(oldRequest: Request, key: String, value: String): Request {
        val params: MutableMap<String, String> = LinkedHashMap()
        params[key] = value
        return resetUrlParams(oldRequest, params)
    }

    /**
     * 重置url上的请求参数
     *
     * @param oldRequest 拦截的旧请求
     * @param params     参数
     * @return
     */
    fun resetUrlParams(oldRequest: Request, params: Map<String, String>): Request {
        val url: String = createUrlFromParams(parseUrl(oldRequest.url.toString()), params)
        return oldRequest.newBuilder().url(url).build()
    }

    /**
     * 刷新url上的请求参数
     *
     * @param oldRequest 拦截的旧请求
     * @param key        请求的key
     * @param value      参数值
     * @return
     */
    fun updateUrlParams(oldRequest: Request, key: String, value: String): Request {
        return oldRequest.newBuilder()
            .url(updateUrlParams(oldRequest.url.toString(), key, value))
            .build()
    }

    /**
     * 刷新url上的请求参数
     *
     * @param oldRequest 拦截的旧请求
     * @param params     刷新的参数集合
     * @return
     */
    fun updateUrlParams(oldRequest: Request, params: Map<String, String>): Request {
        return oldRequest.newBuilder().url(updateUrlParams(oldRequest.url.toString(), params)).build()
    }

    /**
     * 更新url中的参数[保证参数不重复]
     *
     * @param url   请求的url
     * @param key   请求的key
     * @param value 参数值
     * @return
     */
    fun updateUrlParams(url: String, key: String, value: String): String {
        val params: MutableMap<String, String> = LinkedHashMap()
        params[key] = value
        return updateUrlParams(url, params)
    }

    /**
     * 更新url中的参数[保证参数不重复]
     *
     * @param url    请求的url
     * @param params 更新的参数
     * @return
     */
    fun updateUrlParams(url: String, params: Map<String, String>): String {
        val newParams: MutableMap<String, String> = LinkedHashMap()
        val oldParams = getUrlParams(url)
        if (oldParams != null) {
            newParams.putAll(oldParams)
        }

        params.forEach { (key, value) -> newParams[key] = value }

        val sb = StringBuilder()
        sb.append(parseUrl(url)).append("?")
        try {
            newParams.forEach { (key, value) ->
                val urlValue: String = URLEncoder.encode(value, UTF8.name())
                sb.append(key).append("=").append(urlValue).append("&")
            }
            sb.deleteCharAt(sb.length - 1)
            return sb.toString()
        } catch (e: UnsupportedEncodingException) {
            Timber.e(e)
        }
        return url
    }


    /**
     * 解析前：https://xxx.xxx.xxx/app/chairdressing/skinAnalyzePower/skinTestResult?appId=10101
     * 解析后：https://xxx.xxx.xxx/app/chairdressing/skinAnalyzePower/skinTestResult
     *
     * @param url
     * @return
     */
    fun parseUrl(url1: String): String {
        // 如果URL不是空字符串
        var url = url1
        if ("" != url && url.contains("?")) {
            url = url.substring(0, url.indexOf('?'))
        }
        return url
    }

    /**
     * 将参数拼接到url中
     *
     * @param url    请求的url
     * @param params 参数
     * @return
     */
    fun createUrlFromParams(url: String, params: Map<String, String>): String {
        try {
            val sb = StringBuilder()
            sb.append(url)
            if (url.indexOf('&') > 0 || url.indexOf('?') > 0) {
                sb.append("&")
            } else {
                sb.append("?")
            }
            params.forEach { (key, value) ->
                //对参数进行 utf-8 编码,防止头信息传中文
                val urlValue: String = URLEncoder.encode(value, UTF8.name())
                sb.append(key).append("=").append(urlValue).append("&")
            }
            sb.deleteCharAt(sb.length - 1)
            return sb.toString()
        } catch (e: UnsupportedEncodingException) {
            Timber.e(e)
        }
        return url
    }

    /**
     * 获取URL中参数 并返回Map[LinkedHashMap(有顺序)]
     *
     * @param url
     * @return
     */
    fun getUrlParams(url: String): Map<String, String>? {
        var params: MutableMap<String, String>? = null
        try {
            val urlParts = url.split("\\?").toTypedArray()
            if (urlParts.size > 1) {
                params = LinkedHashMap()
                val query = urlParts[1]
                for (param in query.split("&").toTypedArray()) {
                    val pair = param.split("=").toTypedArray()
                    val key: String =
                        URLDecoder.decode(pair[0], UTF8.name())
                    var value = ""
                    if (pair.size > 1) {
                        value =
                            URLDecoder.decode(pair[1], UTF8.name())
                    }
                    params[key] = value
                }
            }
        } catch (ex: UnsupportedEncodingException) {
            ex.printStackTrace()
        }
        return params
    }

    //上传相关
    fun upLoadMultipartBody(file: File): MultipartBody {
        val builder: MultipartBody.Builder = MultipartBody.Builder().setType(MultipartBody.FORM) //表单类型

        val map: Map<String, RequestBody> = HashMap()

        //"image/png" 是内容类型，后台设置的类型
        val requestBody: RequestBody = RequestBody.create("multipart/form-data".toMediaTypeOrNull(), file)

        builder.addFormDataPart("name", file.name)
        builder.addFormDataPart("size", "" + file.length())
        /*
         * 这里重点注意：
         * filename是文件的名字，包含后缀名的   比如:abc.png
         */builder.addFormDataPart("file", file.name, requestBody)
        return builder.build()
    }


    fun upLoadCuttingMultipartBody(file: File, currentBlock: Int, uploadName:String): MultipartBody?{
        val fileStream: ByteArray? = FileExt.cutFile(file, currentBlock - 1)
        if (fileStream == null) {
            Timber.d( "uploadMedia: getBlock error")
            return null
        }

        val builder: MultipartBody.Builder = MultipartBody.Builder().setType(MultipartBody.FORM) //表单类型

        val requestBody = RequestBody.create(MultipartBody.FORM, fileStream)
        builder.addFormDataPart("name", uploadName)
        builder.addFormDataPart("size", "" + fileStream.size)
        Timber.d("size" + fileStream.size)
        builder.addFormDataPart("num", "" + currentBlock)

        /*
         * 这里重点注意：
         * filename是文件的名字，包含后缀名的   比如:abc.png
         */
        builder.addFormDataPart("file", file.name, requestBody)
        return builder.build()
    }

}

fun OkHttpClient.downloadMediaFile(
    url: String,
    downloadName: String,
    file: File,
    ioResultListener: ProgressListener,
) = K.runOnScope {
    if (file.exists()) {
        downloadMedia(url, downloadName, file, 1, ioResultListener)
    } else {
        Timber.i( "文件不存在")
        ioResultListener.onError("-1", "文件不存在")
    }
}.onCancel {
    ioResultListener.cancel()
}

fun <T> OkHttpClient.downloadMediaFile(
    url: String,
    osFactory: OutputStreamFactory<T>,
    append: Boolean = false,
    ioResultListener: ProgressListener,
){

}

private suspend fun OkHttpClient.downloadMedia(
    url: String,
    uploadName: String,
    file: File,
    currentBlock: Int,
    ioResultListener: ProgressListener
) {

}


fun OkHttpClient.uploadMediaFile(
    url: String,
    uploadName: String,
    file: File,
    appInfo: String?=null,
    ioResultListener: ProgressListener,
    //基础的裁剪大小20m
    baseCuttingSize: Long = (20 * 1024 * 1024).toLong()
) = K.runOnScope {

    if (file.exists()) {
        //总的分块数
        var sumBlock = (file.length() / baseCuttingSize).toInt()
        if (file.length() % baseCuttingSize != 0L) {
            sumBlock += 1
        }
        uploadMedia(url, uploadName, file, appInfo, 1,sumBlock, ioResultListener)
    } else {
        Timber.i( "文件不存在")
        ioResultListener.onError("-1", "文件不存在")
    }
}.onCancel {
    ioResultListener.cancel()
}


private suspend fun OkHttpClient.uploadMedia(
    url: String,
    uploadName: String,
    file: File,
    appInfo: String?,
    currentBlock: Int,
    sumBlock:Int,
    ioResultListener: ProgressListener
) {
    val body = OkHttpExt.upLoadCuttingMultipartBody(file, currentBlock, uploadName) ?:return

    val request = OkHttpExt.post(url, body = body)
    callWithCounting(request, countDownload = false) { response ->
        if (!response.isSuccessful) {
            throw ResponseStatusException(response.code)
        }
    }.collect { state ->
        when (state) {
            is CallState.Uploading -> {
                println("Uploading: ${state.bytesTransferred} bytes sent." +
                        state.progressPercentage?.let { " ($it%)" }.orEmpty()
                )
            }
            is CallState.Downloading -> Unit
            is CallState.Success -> {
                println("The upload is complete. TotalLength=${state.bytesUploaded}")
                val progress: Double = 100 * Math.div(currentBlock.toDouble(), sumBlock.toDouble(), 2)
                ioResultListener.onProgress(sumBlock.toLong(),progress.toLong())
                if (currentBlock < sumBlock) {
                    delay(100)
                    uploadMedia(url, uploadName, file, appInfo, currentBlock + 1, sumBlock, ioResultListener)
                    return@collect
                }
                ioResultListener.onSuccess()
            }
            is CallState.Failure -> {
                println("Failure: ${state.exception}")
                ioResultListener.onError(uploadName,"${state.exception}")
            }
        }
    }
}


/**
 * Executes the given OkHttp [request] and then handles its response with the [responseHandler].
 *
 * This extension function is semantically equivalent to
 * `OkHttpClient.newCall(request).execute().use(responseHandler)`,
 * but with the following advantages:
 *
 * - All I/O operations are executed on OkHttp's background threads, and the current thread is not
 * blocked. So you can call this function from any coroutines without using
 * `withContext(Dispatchers.IO) { ... }`.
 * - Handles coroutine cancellations properly. If the current coroutine is cancelled, it immediately
 * cancels the HTTP call and throws a `CancellationException`.
 *
 * This is a sample code that fetches a content of the given URL as `String`.
 * ```
 * suspend fun fetchContentAsString(url: String): String {
 *     val request = Request.Builder().url(url).build()
 *     return okHttpClient.call(request) { response ->
 *         if (!response.isSuccessful) {
 *             throw ResponseStatusException(response.code)
 *         }
 *         checkNotNull(response.body).string()
 *     }
 * }
 * ```
 *
 * @param request an OkHttp [Request] to be executed.
 * @param responseHandler a function to process an OkHttp [Response]. The response object will be
 * closed automatically after the function call. This function is called from a background thread.
 * @return the result of [responseHandler].
 * @throws IOException
 */
suspend fun <T> OkHttpClient.call(request: Request, responseHandler: (Response) -> T): T =
    suspendCancellableCoroutine { cont ->
        val call = newCall(request)
        cont.invokeOnCancellation { call.cancel() }
        call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                val result = runCatching { response.use(responseHandler) }
                cont.resumeWith(result)
            }

            override fun onFailure(call: Call, e: IOException) {
                cont.resumeWithException(e)
            }
        })
    }


/**
 * Creates a [Flow] that executes an HTTP call with counting the number of bytes transferred in its
 * request and response body. Through the [Flow], you can see the progress of the HTTP call.
 *
 * This is a sample code that sends the content of `fileToUpload` as an HTTP POST method.
 * ```
 * suspend fun performUploadWithProgress(url: HttpUrl, fileToUpload: File) {
 *     val request = Request.Builder()
 *         .url(url)
 *         .post(fileToUpload.asRequestBody("application/octet-stream".toMediaType()))
 *         .build()
 *     okHttpClient.callWithCounting(request, countDownload = false) { response ->
 *         if (!response.isSuccessful) {
 *             throw ResponseStatusException(response.code)
 *         }
 *     }.collect { state ->
 *         when (state) {
 *             is Uploading ->
 *                 println("Uploading: ${state.bytesTransferred} bytes sent." +
 *                     state.progressPercentage?.let { " ($it%)" }.orEmpty())
 *             is Downloading -> Unit
 *             is Success ->
 *                 println("The upload is complete. TotalLength=${state.bytesUploaded}")
 *             is Failure ->
 *                 println("Failure: ${state.exception}")
 *         }
 *     }
 * }
 * ```
 *
 * This is a sample code that downloads the content of `url` using an HTTP GET method, and saves it
 * to `fileToSave`.
 * ```
 * suspend fun performDownloadWithProgress(url: HttpUrl, fileToSave: File) {
 *     val request = Request.Builder().url(url).build()
 *     okHttpClient.callWithCounting(request) { response ->
 *         if (response.code != StatusCode.OK) {
 *             throw ResponseStatusException(response.code)
 *         }
 *         response.saveBodyToFileAtomically(fileToSave)
 *     }.collect { state ->
 *         when (state) {
 *             is Uploading -> Unit
 *             is Downloading ->
 *                 println("Downloading: ${state.bytesTransferred} bytes received." +
 *                     state.progressPercentage?.let { " ($it%)" }.orEmpty())
 *             is Success ->
 *                 println("The download is complete. TotalLength=${state.bytesDownloaded}")
 *             is Failure ->
 *                 println("Failure: ${state.exception}")
 *         }
 *     }
 * }
 * ```
 *
 * @param request An OkHttp [Request] to be executed.
 * @param countUpload If `false`, don't count the number of bytes in the request body.
 * @param countDownload If `false`, don't count the number of bytes in the response body.
 * @param handlePartialResponse If `true`, includes the `Content-Range` header value of a partial
 * response in [CallState.Downloading] and [CallState.Success].
 * @param throttleMillis Limits the emission of [CallState.Uploading] or [CallState.Downloading] to
 * at most once every millisecond of this value.
 * @param responseHandler A function to process an OkHttp [Response]. The response object will be
 * closed automatically after the function call. This function is called from a background thread of
 * OkHttp's thread pool.
 * @return A [Flow] that emits the progress of the HTTP call.
 * @see Response.saveBodyToFileAtomically
 * @see Response.saveToResourceWithSupportingResumption
 */
fun <T> OkHttpClient.callWithCounting(
    request: Request,
    countUpload: Boolean = true,
    countDownload: Boolean = true,
    handlePartialResponse: Boolean = true,
    throttleMillis: Long = 100L,
    responseHandler: (Response) -> T
): Flow<CallState<T>> {
    val unthrottledFlow = callbackFlow {
        val countingRequestBody = request.takeIf { countUpload }?.body?.let { CountingRequestBody(it, channel) }
        val call = newCall(countingRequestBody?.injectTo(request) ?: request)
        call.enqueue(
            CountingCallback(
                countingRequestBody,
                countDownload,
                handlePartialResponse,
                responseHandler,
                channel
            )
        )
        awaitClose(call::cancel)
    }.conflate()
    return if (throttleMillis > 0) {
        unthrottledFlow.onEach { if (it is TransferProgress) delay(throttleMillis) }
    } else unthrottledFlow
}